﻿using System;
using CatClient.Message;
using CatClient.Util;
using PostSharp.Aspects;
using PostSharp.Aspects.Internals;
using PostSharp.Extensibility;
using CatClient;
using System.Diagnostics;
using System.Text;
using System.Linq;
using Newtonsoft.Json;
using System.Data;
using System.Data.SqlClient;

namespace PsCat.Aspet
{
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
    [Serializable]
    public class SqlCatAttribute : OnMethodBoundaryAspect
    {
        public string Name { get; private set; }

        public string Method { get; private set; }

        public SqlCatAttribute(string name = null, string sqlMethod = null)
        {
            this.Name = name;
            this.Method = sqlMethod;
        }

        public override void OnEntry(MethodExecutionArgs args)
        {
            try
            {
                if (!Cat.IsInitialized())
                    return;

                var sourceMethod = new StringBuilder();
                if (string.IsNullOrWhiteSpace(this.Name))
                {
                    var trace = new StackTrace();
                    string innerEntryMethod = string.Empty, innerEntryClass = string.Empty;
                    for (int i = trace.FrameCount - 1; i >= 0; i--)
                    {
                        var className = trace.GetFrame(i).GetMethod().DeclaringType?.ToString();
                        if (string.IsNullOrEmpty(className))
                            continue;

                        if (className.StartsWith("Cat") || className.EndsWith("Attribute"))
                            continue;

                        if (className.Contains("System") || className.Contains("SQLHelper") || className.Contains("aspx"))
                            continue;

                        sourceMethod.AppendLine(className).Append("/");

                        if (className.ToLower().EndsWith("dao"))
                        {
                            var methodName = trace.GetFrame(i).GetMethod().Name;
                            if (string.IsNullOrEmpty(innerEntryMethod))
                            {
                                innerEntryMethod = methodName;

                                string[] classNameTemp = className.Split('.');
                                innerEntryClass = classNameTemp[classNameTemp.Count() - 1];
                            }
                        }

                        if (string.IsNullOrEmpty(this.Method))
                            this.Method = className;
                    }

                    this.Name = $"{ innerEntryClass }/{ innerEntryMethod }";
                }

                args.MethodExecutionTag = Cat.NewTransaction(PureCatConstants.TYPE_SQL, this.Name);

                Cat.LogEvent(PureCatConstants.TYPE_SQL, this.Method, PureCatConstants.SUCCESS, sourceMethod.ToString().TrimEnd('/'));

                if (args.Arguments != null)
                {
                    Func<string, int, string> strLineFeed = (val, len) =>
                    {
                        if (val.Length <= len)
                            return val;

                        StringBuilder builder = new StringBuilder();
                        for (int i = 0; i < (val.Length / len) + ((val.Length % len) == 0 ? 0 : 1); i++)
                        {
                            builder.AppendLine(string.Concat(val.Skip(i * len).Take(len)));
                        }
                        return builder.ToString();
                    };

                    var paramters = args.Method.GetParameters().ToList();
                    if (paramters != null && paramters.Count > 0)
                    {
                        StringBuilder paramtersStr = new StringBuilder();
                        paramters.ForEach(param =>
                        {
                            if (param.Name.ToLower().Contains("commandtext"))
                            {
                                var val = JsonConvert.SerializeObject(args.Arguments[paramters.IndexOf(param)]);
                                val = strLineFeed(val, 100);
                                Cat.LogEvent(PureCatConstants.TYPE_SQL_METHOD, string.Concat(val.Take(100)), PureCatConstants.SUCCESS, val);
                            }

                            if (!param.Name.ToLower().Contains("connection"))
                            {
                                if (param.Name.ToLower().Contains("parameters"))
                                {
                                    var paramObj = args.Arguments[paramters.IndexOf(param)];
                                    if (paramObj != null)
                                    {
                                        if (typeof(SqlParameter[]).Name.Equals(paramObj.GetType().Name))
                                        {
                                            foreach (var item in (SqlParameter[])paramObj)
                                            {
                                                var val = JsonConvert.SerializeObject(item.Value);
                                                val = strLineFeed(val, 100);
                                                paramtersStr.AppendLine($"{ item.ParameterName }={ val }");
                                            }
                                        }
                                        else
                                            paramtersStr.AppendLine($"{ param.Name }={ JsonConvert.SerializeObject(paramObj) }");
                                    }
                                }
                                else
                                {
                                    if (param.ParameterType.IsEnum)
                                        paramtersStr.AppendLine($"{ param.Name }={ param.ParameterType.GetEnumName(args.Arguments[paramters.IndexOf(param)]) }");
                                    else
                                        paramtersStr.AppendLine($"{ param.Name }={ JsonConvert.SerializeObject(args.Arguments[paramters.IndexOf(param)]) }");
                                }
                            }
                        });
                        Cat.LogEvent(PureCatConstants.TYPE_SQL, PureCatConstants.TYPE_SQL_PARAM, PureCatConstants.SUCCESS, paramtersStr.ToString());
                    }
                }

                base.OnEntry(args);
            }
            catch (Exception ex)
            {
                LogInfoWriter.GetInstance("Cat", 0, LogInfoWriter.LogLayout.Default).Error(ex.Message, ex);
            }
        }

        public override void OnExit(MethodExecutionArgs args)
        {
            ITransaction transaction = (ITransaction)args.MethodExecutionTag;
            if (transaction == null)
                return;

            if (args.ReturnValue != null)
            {
                try
                {
                    dynamic returnAttribute = new { };
                    if (args.ReturnValue.GetType().Name.Equals(typeof(int).Name))
                        returnAttribute = new { ReturnValue = JsonConvert.SerializeObject(args.ReturnValue) };

                    if (args.ReturnValue.GetType().Name.Equals(typeof(DataSet).Name))
                        returnAttribute = new { ((DataRowCollection)(((DataSet)args.ReturnValue).Tables[0].Rows)).Count };

                    Cat.LogEvent(PureCatConstants.TYPE_SQL, PureCatConstants.TYPE_SQL_ROWS, PureCatConstants.SUCCESS, JsonConvert.SerializeObject(returnAttribute));
                }
                catch (Exception ex)
                {
                    transaction.SetStatus(ex);
                }
            }

            transaction.Complete();
        }

        [MethodExecutionAdviceOptimization(MethodExecutionAdviceOptimizations.IgnoreGetMethod | MethodExecutionAdviceOptimizations.IgnoreSetFlowBehavior | MethodExecutionAdviceOptimizations.IgnoreGetArguments | MethodExecutionAdviceOptimizations.IgnoreSetArguments | MethodExecutionAdviceOptimizations.IgnoreGetInstance | MethodExecutionAdviceOptimizations.IgnoreSetInstance | MethodExecutionAdviceOptimizations.IgnoreGetException | MethodExecutionAdviceOptimizations.IgnoreGetReturnValue | MethodExecutionAdviceOptimizations.IgnoreSetReturnValue | MethodExecutionAdviceOptimizations.IgnoreSetMethodExecutionTag | MethodExecutionAdviceOptimizations.IgnoreGetYieldValue | MethodExecutionAdviceOptimizations.IgnoreSetYieldValue | MethodExecutionAdviceOptimizations.IgnoreGetDeclarationIdentifier)]
        public override void OnSuccess(MethodExecutionArgs args)
        {
            ITransaction transaction = (ITransaction)args.MethodExecutionTag;
            if (transaction != null)
                transaction.Status = PureCatConstants.SUCCESS;
        }

        [MethodExecutionAdviceOptimization(MethodExecutionAdviceOptimizations.IgnoreGetMethod | MethodExecutionAdviceOptimizations.IgnoreSetFlowBehavior | MethodExecutionAdviceOptimizations.IgnoreGetArguments | MethodExecutionAdviceOptimizations.IgnoreSetArguments | MethodExecutionAdviceOptimizations.IgnoreGetInstance | MethodExecutionAdviceOptimizations.IgnoreSetInstance | MethodExecutionAdviceOptimizations.IgnoreGetReturnValue | MethodExecutionAdviceOptimizations.IgnoreSetReturnValue | MethodExecutionAdviceOptimizations.IgnoreSetMethodExecutionTag | MethodExecutionAdviceOptimizations.IgnoreGetYieldValue | MethodExecutionAdviceOptimizations.IgnoreSetYieldValue | MethodExecutionAdviceOptimizations.IgnoreGetDeclarationIdentifier)]
        public override void OnException(MethodExecutionArgs args)
        {
            ITransaction transaction = (ITransaction)args.MethodExecutionTag;
            if (transaction != null)
                transaction.SetStatus(args.Exception);

            throw args.Exception;
        }
    }
}
